Skip to content

feat(devx): gate the os package publish example set against its documented block - #15721

Merged
baozhoutao merged 3 commits into
mainfrom
claude/issue-15393-publish-examples-parity
Sep 5, 2026
Merged

feat(devx): gate the os package publish example set against its documented block#15721
baozhoutao merged 3 commits into
mainfrom
claude/issue-15393-publish-examples-parity

Conversation

@claude

@claude claude Bot commented Sep 5, 2026

Copy link
Copy Markdown
Contributor

Fixes #15393

os package publish ships its example set twice — the oclif examples array in
packages/cli/src/commands/package/publish.ts, printed verbatim under EXAMPLES, and a
fenced block under #### os package publish in content/docs/deployment/cli.mdx. Two
hand-maintained copies of one set, and nothing compared them. They came apart: the CLI
line carried # local dev (apps/cloud), pointing at a directory deleted from this repo,
while the docs line for the same example was correct the whole time the shipped --help
output was wrong. PR #15390 made them agree again; this is the mechanism half.

No change to publish.ts or cli.mdx — the two sides already agree, and this PR
measures that rather than asserting it.

What ships

  • scripts/check-cli-examples-parity.mjs — the gate, with --list and --self-test.
  • package.json — one row, check:cli-examples-parity, --self-test && gate, next to
    check:cli-command-ids.
  • .github/workflows/lint.yml — one step in the Lint & Repo Gates job, immediately
    after the CLI command-id literals resolve step. No build, no extra install: the gate
    reads two source files.

⛔ Not an extension of check-cli-command-ids

That gate considered this same docs page and refused, on the record at
scripts/check-cli-command-ids.mjs:99-106: extending it over prose would mean deciding,
with no delimiter to lean on, which of 811 os ... mentions in content/docs is a
command and which is a sentence. That refusal is correct, it stands untouched, and this
PR does not reopen it. The new gate's header says so in its own words.

The difference is the population. This one is a table, not a corpus:

command file examples symbol docs page heading
packages/cli/src/commands/package/publish.ts examples content/docs/deployment/cli.mdx #### os package publish

One fenced block under one exact heading, against one named array in one named file. No
prose is scanned and no line enters the comparison that a row did not point at. A second
pair is one more row.

The equality rule

The two voices legitimately differ — help text is terse, docs prose annotates, the CLI
writes $ prompts, the docs block aligns its comments into a column, and the orders
differ today (--install is third in the CLI and fourth in the docs). So what is
compared is the set of invocations, each normalised by:

  • stripping a leading $ prompt;
  • stripping a trailing # comment, only where a shell would take it — at line start or
    after whitespace, and outside quotes, so the # in --note "a # b" stays part of the
    invocation;
  • trimming, and collapsing internal runs of spaces and tabs to one space.

Order is ignored. Commentary is ignored. The invocations may not differ.

The one definitional call, measured before it was made

The docs block carries six lines; the examples array carries five. The extra
docs line is os compile — the step a reader runs before publishing, in a block
written to be pasted whole. It is not a sixth way to invoke os package publish, and an
oclif examples entry is by construction an example of the command it is declared on,
so the array could not carry it.

So the row's command phrase — derived from the command file's path plus the owning
package's declared oclif.bin, never typed a second time — selects which fence lines are
compared. A fence line that does not invoke that phrase is a context line: counted,
named in the verdict, not compared. Comparing every fence line instead would assert
something false about docs blocks (that a teaching transcript may contain nothing but
invocations of one command) and would red today over a line nobody thinks is wrong.

The asymmetry runs the other way on the CLI side, deliberately: an examples entry that
does not invoke the row's command is a finding, never a silently skipped line.

⚠️ Reviewers: this is the judgement call in the PR. If the ruling is that every fence
line must match, that makes today's tree red and the repair lands in cli.mdx, which
this lane treats as read-only — say so and it comes back as a decision, not a patch.

Refusals, never quiet passes

Exit 1 naming the row for: an empty table; a command file, docs page or package manifest
that cannot be read; an examples symbol that is absent or is not a plain array of
string literals (a computed array or an interpolating template is refused, not guessed
at); a heading that is absent or ambiguous; a heading with no fenced block before the
next heading; and zero invocations on either side — the anti-vacuity case, where two
empty sets would otherwise be equal and print a confident green.

The CommonMark fence scanner is imported from
scripts/docs-audit/check-docs-transcript-drift.mjs rather than re-derived, which also
records the coupling where scripts/pm/dispatch-gates.mjs can see it.

Measured on this tree

node scripts/check-cli-examples-parity.mjs — exit 0:

os-package-publish: 5 `os package publish` invocation(s) in
packages/cli/src/commands/package/publish.ts (`examples`) == 5 in the block at
content/docs/deployment/cli.mdx:1627 under #### `os package publish`
(1 context line(s) skipped)
check-cli-examples-parity: 1 declared pair(s) agree as SETS (order, prompts and
trailing comments normalised away).

--list prints both sides; the five normalised invocations are identical strings on
both, and os compile is the single context line.

Self-test

node scripts/check-cli-examples-parity.mjs --self-test — exit 0, 49 cases, three
batteries under a declared roster and floor (the shape the sibling gates use):

  • the normaliser, the string-literal examples reader and its refusals, the
    heading/fence locator — 22 cases;
  • the judgement over real scratch trees on disk, including a second table row
    judged in the same run as the first (one green, one red, each finding attributed to
    its own row) — 16 cases;
  • the program level: real trees, the real file run as a child process, real exit
    codes — equal sets in a different order with different comments exit 0; an invocation
    missing on one side exits 1 naming it; a docs block with zero invocations of the
    command exits 1; a moved heading exits 1; an absent examples symbol exits 1 — 8
    cases.

Ablation (trap-guarded, absolute paths, mutation proven on disk both directions)

Removed the trailing-comment normalisation (text = stripTrailingComment(text);, one
occurrence, replaced by a marker):

HEAD blob:           63fbfe9e3e1f2f778a182a085632f66b52e09446
pre-mutation hash:   63fbfe9e3e1f2f778a182a085632f66b52e09446
anchor count before: 1     anchor count after: 0     marker count after: 1
mutated hash:        3e0f725008da9b91b40dfed04d9ffc7a56714081
ABLATED_PRODUCTION_EXIT=1     (6 findings: every annotated line stops matching)
ABLATED_SELFTEST_EXIT=1
restored hash:       63fbfe9e3e1f2f778a182a085632f66b52e09446   == HEAD blob
git diff HEAD:       (empty)
RESTORED_PRODUCTION_EXIT=0

Dispatch derivation, before and after

node scripts/pm/dispatch-gates.mjs --commands PATH --repo objectstack-ai/objectstack,
both sides of the pair (exit 0 each):

path before after
packages/cli/src/commands/package/publish.ts not derived pnpm check:cli-examples-parity
content/docs/deployment/cli.mdx not derived pnpm check:cli-examples-parity

The table rows are the path literals that place it, which is why both sides name it.

The derived family on the final head

node scripts/pm/dispatch-gates.mjs --changed --commands --repo objectstack-ai/objectstack
derives 53 commands for this diff; all were run. Green includes
check:cli-command-ids, check-self-test-wired (+ --self-test),
check-self-test-workflow-commands (+ --self-test), check:ci-filter-parity,
check:watch-hint-literal, check:declared-population-live, check:pm-dispatch-gates,
check:nul-bytes, check-aggregator-roster, check-declaration-mirrors,
check-step-collectors, check:required-contexts, check:parse-guard,
check:entry-guard and check:turbo-task-graph.

check:cli-command-ids did red on the first draft — two self-test fixtures were
quoted phrases opening with a real bin name and resolving to nothing. Reworded (a
neutral binary for the word-boundary case, a plain heading for the absent-heading case);
green since.

⊘ NOT MEASURED

  • check:dual-build-cjs-loads and check:type-check-debt — both exit 3,
    PREREQUISITE NOT MET: they read built output and this tree has no dist/. Not a
    pass and not a finding. Neither is implicated by this diff (both are derived from
    package.json).
  • The workflow-variable families dispatch-gates prints as ⊘ NOT MEASURED
    (check-shard-attestation, check-test-completeness, the --union-into limb of
    check-cross-package-test-inputs) — their argv carries values that exist only on a
    runner.
  • pnpm lint repo-wide: narrowed, and the narrowing is measured, not assumed.
    (1) The population comes from eslint's own flat config, not from a guess: run over the
    three changed paths, eslint reports File ignored because no matching configuration was supplied for package.json and .github/workflows/lint.yml — they are outside
    its population entirely. (2) The count comes from --format json: 3 files passed in,
    1 linted, 0 errors, 0 warnings on scripts/check-cli-examples-parity.mjs.
    (3) Invariance for untouched files: the flat config enables no type-aware linting
    (eslint.config.mjs:328 states it, and no parserOptions.project exists in the
    file), so this diff cannot move the verdict on any file it does not contain.
  • No changeset: skip-changeset. Nothing here is published from any package — a gate
    script, a root package.json script row and a CI step.

Scope

The card's disposition was gate it and nothing else. publish.ts and cli.mdx are
untouched, scripts/check-cli-command-ids.mjs is untouched, and no bare token sweep of
objectos or cloud was performed anywhere — that trap is real and is recorded on
#14806.

🤖 Generated with Claude Code

https://claude.ai/code/session_012zGPuVVX3deAx9LdjK8jCk


Generated by Claude Code

…umented block

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012zGPuVVX3deAx9LdjK8jCk
…terals

`check:cli-command-ids` reads any quoted run opening with a real oclif bin name
as a command-id literal, and reds when it resolves to nothing. Two fixtures in
the new parity gate's self-test were deliberately-unresolvable phrases.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012zGPuVVX3deAx9LdjK8jCk
…hake

The handshake guards a `return` that skips the verdict block, not the verdict's
direction; leaving the flag false on a red printed "never finished" over a run
that finished and failed.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012zGPuVVX3deAx9LdjK8jCk
@claude claude Bot added the skip-changeset PR has no user-facing published change; bypasses the changeset gate label Sep 5, 2026
@github-actions github-actions Bot added size/l ci/cd dependencies Pull requests that update a dependency file labels Sep 5, 2026
@claude

claude Bot commented Sep 5, 2026

Copy link
Copy Markdown
Contributor Author

ACCEPT — PM seat domain:devx @ objectstack (#6023), session session_012zGPuVVX3deAx9LdjK8jCk.

Reviewed against the three-dot diff at 374a14833 (3 files, +973/−0), not the report. Closing keyword: Fixes #15393 only. The two definitional questions are ruled A/A on the card (context lines skipped-and-named; ship).

What the diff does: new scripts/check-cli-examples-parity.mjs — a one-row PAIRS table (packages/cli/src/commands/package/publish.ts examplescontent/docs/deployment/cli.mdx #### \os package publish`), the command phrase derived from the command file path + the package's declared oclif bin (never typed twice), normaliseInvocation(leading$ , trailing #comment, whitespace runs), set equality with order ignored, findings for missing-in-docs / missing-in-cli / a CLI entry that does not invoke the command / an empty side / a moved heading / an absent symbol;--listprints both sets and the context lines. Wired ascheck:cli-examples-parity (--self-test && gate) in root package.jsonand onelint.ymlstep right aftercheck:cli-command-ids(no build needed). Header states it is a DIFFERENT gate fromcheck-cli-command-ids.mjswith a different population; that file,publish.tsandcli.mdx` are untouched.

Measured by this seat on the PR head (plain node, detached worktree; readings copied from the run output):

node scripts/check-cli-examples-parity.mjs --self-test   EXIT=0   ✓ check-cli-examples-parity self-test: 49 cases pass (…)
node scripts/check-cli-examples-parity.mjs               EXIT=0
  ✓ os-package-publish: 5 `os package publish` invocation(s) in packages/cli/src/commands/package/publish.ts (`examples`) == 5 in the block at content/docs/deployment/cli.mdx:1627 under #### `os package publish` (1 context line(s) skipped)
  ✓ check-cli-examples-parity: 1 declared pair(s) agree as SETS (order, prompts and trailing comments normalised away).
node scripts/check-cli-examples-parity.mjs --list        EXIT=0   both sets identical (5 lines each); ctx cli.mdx:1628 `os compile` named

The derived family (53 commands, 50 exit 0, 3 NOT MEASURED by name) is the dev's reading; CI's Lint & Repo Gates is the arbiter (in progress at review time).

Fences held: three files; check-governed-merges.mjs --test0 of 3 path(s) hit the register; git merge-tree origin/main clean (other seats' ready PRs #15392/#15331 touch lint.yml/package.json on other hunks — the queue re-tests); skip-changeset (nothing published). Flip + arm once both required jobs read success.


Generated by Claude Code

@baozhoutao
baozhoutao marked this pull request as ready for review September 5, 2026 04:37
@baozhoutao
baozhoutao enabled auto-merge September 5, 2026 04:37
@baozhoutao
baozhoutao added this pull request to the merge queue Sep 5, 2026
Merged via the queue into main with commit ef67449 Sep 5, 2026
39 checks passed
@baozhoutao
baozhoutao deleted the claude/issue-15393-publish-examples-parity branch September 5, 2026 05:27
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ci/cd dependencies Pull requests that update a dependency file size/l skip-changeset PR has no user-facing published change; bypasses the changeset gate

Projects

None yet

2 participants